<h2 id='tutorial/concepts/$watch.html'>$watch方法</h2>

<p>1.5+</p>
<p>avalon1.5对$watch机制进行大重构, 支持利用通配符*, 解决对数组元素,子属性的监听.注意,*号只能出现一次.</p>
<xmp class="javascript">
    var vm = avalon.define({ 
    $id: "test",
    array: [1, 2, 3],
    arr: [{a: 1}, {a: 2}, {a: 3}],
    obj: {
    a: 1,
    b: 2
    },
    a: {
    b: {
    c: {
    d: 33
    }
    }
    }
    })
    vm.$watch("array.*", function (a, b) {
    expect(a).to.be(6)
    expect(b).to.be(2)
    })
    vm.$watch("arr.*.a", function (a, b) {
    expect(a).to.be(99)
    expect(b).to.be(1)
    })
    vm.$watch("obj.*", function (a, b, c) {
    expect(a).to.be(111)
    expect(b).to.be(1)
    })
    vm.$watch("a.*.c.d", function (a, b, c) {
    expect(a).to.be(88)
    expect(b).to.be(33)
    })
    setTimeout(function () {
    vm.array.set(1, 6)
    vm.arr[0].a = 99
    vm.obj.a = 111
    vm.a.b.c.d = 88
    }, 100)
</xmp>
<p>$watch会返回一个函数,用于解除监听: </p>
<xmp class="javascript">

    var unwatch = vm.$watch("array.*", function (a, b) {
    expect(a).to.be(6)
    expect(b).to.be(2)
    })
    unwatch() //移除当前$watch回调
</xmp>
<hr>
<p>1.5-</p>
<p>此方法是位于VM中，与$unwatch, $fire是一套东西，建议用户不要在同一个VM中调用$fire,很容易造成死循环。格式为：</p>
<xmp class="javascript">
    vm.$watch(prop, function(value, oldValue){
    })
</xmp>
<p>prop为VM中的第一层属性，类型是string,boolean,number这些简单类型或是数组长度，当它们发生改变时，就会执行预先绑定的回调。
    一般地，回调里有<span style="color:red">两个参数</span>，第一个是新值<b>value</b>，第2个是旧值<b>oldValue</b>。
    但prop为<span style="color:orange">$all</span>这个特殊的属性，它会在你修改VM中的任何第一层属性时，触发回调，
    这时回调拥有<span style="color:red">三个参数</span>，第一个是发生变动的属性的名字，第二个是新值，第三个是旧值。</p>
<p>如果目标对象是数组，那么只能监听它的长度变化，并只能跑到avalon.define的外面绑定$watch回调。</p>
<p>如果你想监听二级或三级对象的属性时，需要跑到avalon.define的外面，定位到目标属性的父对象上添加$watch回调。</p>
<xmp class="javascript">
    var model = avalon.define({
    $id: "test",
    data: {
    xxx: 222,
    yyy: 333
    }
    })
    model.data.$watch("xxx", function(a,b){
    //................
    })
</xmp>
<h4>一个简单的例子</h4>
<xmp class="html">
    <!DOCTYPE html>
    <html>
        <head>
            <title>$watch</title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <script src="avalon.js"></script>
            <script>
                var model = avalon.define({
                    $id: "$watch1",
                    prop: "xxx",
                    array: ["aaa", "bbb", "ccc", "ddd"],
                    data: {
                        aaa: "lll"
                    },
                    propWatch: "",
                    arrayWatch: 4,
                    dataAAAWatch: "",
                    click: function () {
                        model.prop = new Date - 1
                        model.data.aaa = new Date - 2
                        model.array.push(new Date - 3)
                    }
                })
                model.$watch("prop", function (a) {
                    model.propWatch = a + " $watch"
                })

                //在avalon.define外面绑定$watch回调
                model.$watch("array.length", function (a) {
                    model.arrayWatch = a + " $watch"
                })
                model.$watch("data.aaa", function (a) {
                    model.dataAAAWatch = a + " $watch"
                })
            </script>
        </head>
        <body>
            <div ms-controller="$watch1">
                <p>prop: {{prop}} --> propWatch: {{propWatch}}</p>
                <p>array.length: {{array.size()}} --> arrayWatch: {{arrayWatch}}</p>
                <p>data.aaa: {{data.aaa}} --> dataAAAWatch: {{dataAAAWatch}}</p>
                <p><button ms-click="click" type="button">点我</button></p>
            </div>
        </body>
    </html>

</xmp>

<fieldset>
    <legend>例子一</legend>
    <div ms-controller="test">
        <p>prop: {{prop}} --> propWatch: {{propWatch}}</p>
        <p>array.length: {{array.size()}} --> arrayWatch: {{arrayWatch}}</p>
        <p>data.aaa: {{data.aaa}} --> dataAAAWatch: {{dataAAAWatch}}</p>
        <p><button ms-click="click" type="button">点我</button></p>
    </div>
</fieldset>
<h3>监听$all特殊属性</h3>
<xmp class="html">

    <!DOCTYPE html>
    <html>
        <head>
            <title>$watch</title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <script src="avalon.js"></script>
            <script>
                var props = ["aaa", "bbb", "ccc", "ddd"]
                var backup = props.concat()
                var model = avalon.define({
                    $id: "$watch2",
                    aaa: "xxx",
                    bbb: "yyy",
                    ccc: "zzz",
                    ddd: "uuu",
                    propWatch: "",
                    click: function () {
                        var index = Math.floor(Math.random() * props.length)
                        var prop = props.splice(index, 1)[0]
                        if (!props.length) {
                            props = backup.concat()
                        }
                        model[prop] = new Date - 1
                    }
                })
                model.$watch("$all", function (name) {
                    if (name !== "propWatch") {
                        model.propWatch = name + "  属性发生改变"
                    }
                })

            </script>
        </head>
        <body>
            <div ms-controller="$watch2">
                <ul>
                    <li>aaa: {{aaa}}</li>
                    <li>bbb: {{bbb}}</li>
                    <li>ccc: {{ccc}}</li>
                    <li>ddd: {{ddd}}</li>
                </ul>
                <p>{{propWatch}}</p>
                <p><button ms-click="click" type="button">点我</button></p>
            </div>
        </body>
    </html>
</xmp>
<fieldset>
    <legend>例子二</legend>
    <div ms-controller="$watch2">
        <ul>
            <li>aaa: {{aaa}}</li>
            <li>bbb: {{bbb}}</li>
            <li>ccc: {{ccc}}</li>
            <li>ddd: {{ddd}}</li>
        </ul>
        <p>{{propWatch}}</p>
        <p><button ms-click="click" type="button">点我</button></p>
    </div>
</fieldset>
<p>与ms-duplex结合使用</p>
<fieldset id="validate"><legend>手机号码格式化</legend>
    <xmp class="html">
        <!DOCTYPE html>
        <html>
            <head>
                <title>avalon by RubyLouvre</title>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width">
                <script src="avalon.js"></script>
                <script>

                var model = avalon.define({
                    $id: "mobile",
                    phone: ""
                })
                model.$watch("phone", function (a) {
                    var b = a.replace(/\s+/g, "")
                    var array = b.split("")
                    var ret = ""
                    for (var i = 0, n = array.length; i < n; i++) {
                        if (i > 10)//不能超过11位
                            break
                        if (i == 3) {
                            ret += " "
                        }
                        if (i == 7) {
                            ret += " "
                        }
                        ret += array[i]
                    }
                    model.phone = ret
                })
                </script>
            </head>
            <body>
                <div ms-controller="mobile">
                    <input ms-duplex="phone" />
                </div>
            </body>
        </html>

    </xmp>
</fieldset>
<fieldset>
    <legend>例子三</legend>
    <div ms-controller="mobile">
        <input ms-duplex="phone" />
    </div>
</fieldset>

<style>
    .msform{
        width:500px;
        padding:5px;
        border: 3px solid #666;
    }
    .msform div{
        padding:2px;
    }
    .msform input{
        width:200px;
    }

    .msform .error{
        color: red;
    }
    .msform input.error{
        border: 1px solid red;
    }
    .msform .ok{
        color: green;
    }
</style>

<script type="text/javascript">
    var model = avalon.define({
        $id: "$watch1",
        prop: "xxx",
        array: ["aaa", "bbb", "ccc", "ddd"],
        data: {
            aaa: "lll"
        },
        propWatch: "",
        arrayWatch: 4,
        dataAAAWatch: "",
        click: function () {
            model.prop = new Date - 1
            model.data.aaa = new Date - 2
            model.array.push(new Date - 3)
        }
    })
    model.$watch("prop", function (a) {
        model.propWatch = a + " $watch"
    })

    //在avalon.define外面绑定$watch回调
    model.$watch("array.length", function (a) {
        model.arrayWatch = a + " $watch"
    })
    model.$watch("data.aaa", function (a) {
        model.dataAAAWatch = a + " $watch"
    })
            ;
    (function () {
        var props = ["aaa", "bbb", "ccc", "ddd"]
        var backup = props.concat()
        var model = avalon.define({
            $id: "$watch2",
            aaa: "xxx",
            bbb: "yyy",
            ccc: "zzz",
            ddd: "uuu",
            propWatch: "",
            click: function () {
                var index = Math.floor(Math.random() * props.length)
                var prop = props.splice(index, 1)[0]
                if (!props.length) {
                    props = backup.concat()
                }
                model[prop] = new Date - 1
            }
        })
        model.$watch("$all", function (name) {
            if (name !== "propWatch") {
                model.propWatch = name + "  属性发生改变"
            }
        })
    })();
    var model3 = avalon.define({
        $id: "mobile",
        phone: ""
    })
    model3.$watch("phone", function (a) {
        var b = a.replace(/\s+/g, "")
        var array = b.split("")
        var ret = ""
        for (var i = 0, n = array.length; i < n; i++) {
            if (i > 10)//不能超过11位
                break
            if (i == 3) {
                ret += " "
            }
            if (i == 7) {
                ret += " "
            }
            ret += array[i]
        }
        model3.phone = ret
    })
</script>
